
	/***********************************************************************************************
	  
		Class: OptionMap
		
		Methods:
			map			OptionMap			()											- sid: 3.0
			map			addOption			(id, data/function*)		- sid: 3.0
			map			addOptions		(omap, options*)				- sid: 3.0
			data		getOptionData	(id)										- sid: 3.0

			map			setOption			(id, value)							- sid: 3.0
			v				getOption			(id)										- sid: 3.0
			v				getOldOption	(id)										- sid: 3.0
			b				isOption			(id)										- sid: 3.0

			options	getOptions		(param*)								- sid: 3.0
			map			setOptions		(options, param*)				- sid: 3.0

		Legend:
			id			qid#param
			data		{ dvalue*, setter*, getter*, exposed*, id*, name*, parent* }
			setter	f(v, oldv, id, param)
			getter	f(v, oldv, id, param)
			
	************************************************************************************************/

	//-------------------------------------------------------------------------------
	jsl.Class("jsl.OptionMap", {

		//Private vars
		//-------------------------------------------------------------------------------
		__options: 	null,
					
		//-------------------------------------------------------------------------------
		OptionMap: function() {//@d[
			if(arguments.length) throw jsl.ParamError()//@d]					
			this.__options = []
		},
		//-------------------------------------------------------------------------------
		addOption: function(id, data) {		
			//@sys:	Firefox - setter and getter var names -> syntax error (also in jslint). No error for object property names
			//			jscoverage throw a syntax error in some obscure cases when setter/getter are used also for object properties
			//			setter/getter are(?) js2.0 keyword
			var name, pid, pdata, value, setterM, getterM, exposed, fname, odata, obj
			//@d[
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")
			if(this["__o_" + id])	throw jsl.ParamError("AlreadyDefined:id")//@d]
									
			if(id.indexOf(".") !== -1) {
        
        name  	= id.slice(id.lastIndexOf(".") + 1)
				pid			= id.slice(0, id.lastIndexOf("."))
				pdata 	= this["__o_" + pid]
				obj			= pdata.obj
				
				//@d[									
        if(!pdata)															throw jsl.ParamError("NotDefined: id(parent)")
        if(!jsl.isObject(this.getOption(pid)))	throw jsl.ParamError("BadType:    id(parent):object")//@d]          
			}
      else {
        name 	= id
				obj		= this 
			}
			
			//1° - no data
			if(arguments.length === 1)
				value = {}
			
			//2° - function	
			else if(jsl.isFunction(data)) {
				value		= null
				setterM	= data
			}	
			//3° - data	
			else { 
				//@d[
				if(!jsl.isObject(data))	throw jsl.ParamError("BadType:data:object | function ")//@d]
				
				//@sys: jscoverage throw a syntax error if setter is write as property
				value		= "dvalue" in data ? data.dvalue : null						
				setterM	= data["setter"]
				getterM	= data["getter"]
				
				//@d[
				if(setterM)	jsl.validate(setterM, "f", "data.setter")
				if(getterM)	jsl.validate(getterM, "f", "data.getter")//@d]					
								
				if(data.exposed != null) { 
					
					exposed = data.exposed
					if(!jsl.isBoolean(exposed)) throw jsl.ParamError("BadType:data.exposed:boolean")
					
					if(!!+exposed) {	
					
						fname = id.replace(/^|\../g, function(c){ return c.length === 1 ? c.toUpperCase() : "." + c.toUpperCase() })
					
						obj["set" + fname] = function(value, param) { 
							try 			{ //@d[
													if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()//@d]
													return this.setOption(id + (arguments.length === 2 ? "#" + param : ""), value) 
												} 
							catch(e) 	{ jsl.throwError(e, this, arguments) }
						}					
						obj["get" + fname]	= function(param) { 
							try 			{ //@d[
													if(arguments.length > 1)	throw jsl.ParamError()//@d]
													return this.getOption(id + (arguments.length === 2 ? "#" + param : "")) 
												} 
							catch(e) 	{ jsl.throwError(e, this, arguments) }
						}
					}
				}
			}
			
			odata = {
								id:				id 		+ "" 			,
		            name:			name  + "" 			,
		            parent:		pdata		|| null ,
								setter:	 	setterM	|| null	,
								getter:		getterM	|| null ,
								dvalue:		value						,
								exposed:	!!+exposed,
								childs:		[],		
								obj:			obj						
							}
			
			this["__ov_" 	+ id]	= odata.dvalue
			this["__oov_" + id]	= odata.dvalue

			obj["__o_" + id] 	= odata
			obj.__options.push(odata)						
			if(pid) pdata.childs.push(odata)
			
			return this
	  },			
		//-------------------------------------------------------------------------------
		addOptions: function(omap, options) {
			var os, map, a, len, id, i
			//@d[
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(omap, "o", "omap")
			if(!jsl.OptionMap.isClassOf(omap))						throw jsl.ParamError("BadType:omap:jsl.OptionMap")
			if(omap === this)															throw jsl.ParamError("BadValue:omap:omap = this")				
			if(arguments.length === 2)										jsl.validate(options, "al", "options")//@d]
			
			if(arguments.length === 1) {
			
				os	= omap.__options
				map	= omap.__proto
				
				while(map) {				
					
					//we can have some discontinuations (not jsl.OptionMap object) in the chain but we must go on 				
					if(map.__options)
						os = os.concat(map.__options)

					map	= map.__proto					
				}
				
				//no sort or unique, no duplicates and the options are added in order: parent and next childs
				for(a = 0, len = os.length; a < len; a++) 				
					if(!this["__o_" + os[a].id]) {	
						
						i = os[a].id.lastIndexOf(".")
						//a sub options has always a parent of type object
						if(i !== -1)	this["__o_" + os[a].id.slice(0, i)].obj.addOption(os[a].id, os[a])
						else					this.addOption(os[a].id, os[a])
					}
			}			
			else {
			
				//we must define first the parents and then the childs, ex: ["A.a", "A"] must be sorted in ["A", "A.a"]
				os = options.slice().sort()
				
				for(a = 0, len = os.length; a < len; a++) {
					
					id = os[a]
					//@d[
					jsl.validate(id, "qid", "options[" + a + "]")													
					if(!omap["__o_" + id])	throw jsl.ParamError("NotDefined:omap(option:" + id + ")")//@d]
					
					i = id.lastIndexOf(".")
					//@d[
					if(i !== -1 && !this["__o_" + id.slice(0, i)])								throw jsl.ParamError("NotDefined:id(this.parent)")
					if(i !== -1 && !jsl.isObject(this["__o_" + id.slice(0, i)]))	throw jsl.ParamError("OperationDenied:options[" + a + "]")//@d]			
					
					if(i !== -1)	this["__o_" + id.slice(0, i)].obj.addOption(id, omap["__o_" + id])										
					else 					this.addOption(id, omap["__o_" + id])			
				}
			}
			
			return this
		},			
		//-------------------------------------------------------------------------------
		getOptionData: function(id) {
			var data
			//@d[
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")//@d]
			
			data = this["__o_" + id]//@d[								
			if(!data)	throw jsl.ParamError("NotDefined:id")//@d]

			return { 
               id:    	  		data.id     	,   
               name:   		 		data.name   	, 
               setter: 			 	data.setter 	, 
               getter: 				data.getter 	,
               exposed:				data.exposed	,
               dvalue:				data.dvalue		,
							 parent:				data.parent ? data.parent.id : null				
             }
	  },
		//-------------------------------------------------------------------------------
		setOption: function(id, value) {
  		var data, nv, ov, v, cdata, i, oid, param
			//@d[
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")//@d]
			
			i			= id.indexOf("#")
			oid 	= id.slice(0, i === -1 ? id.length : i)
			param	= id.slice(i === -1 ? id.length : i + 1)
			
			//@d[
			jsl.validate(oid, "qid", "id")//@d]

			data = this["__o_" + oid]//@d[
			if(!data)	throw jsl.ParamError("NotDefined:id")//@d]
			
			this["__oov_" + oid] = this["__ov_" + oid]
			
			//removes the option value on the map (a depth value can exists in th map chain) 
			if(arguments.length === 1)	delete this["__ov_" + oid]				
			else												this["__ov_" + oid] = value
			
			nv = this["__ov_"  + oid]
			ov = this["__oov_" + oid]
				
			try				{ v = data.setter ? data.setter.call(this, nv, ov, oid, param) : nv										}
			catch(e)	{ throw e.trace(this.getName() + ".set(v, oldv, id, param)", [ nv, ov, oid, param ])	}
			
			this["__ov_"  + oid] = v
      if(data.childs.length && !jsl.isObject(v)) throw jsl.Error("BadReturnType:setter:object")
      
			param = param ? "#" + param : param
			for(var a = 0, len = data.childs.length; a < len; a++) {
			
				cdata = data.childs[a]
				
				if(v.hasOwnProperty(cdata.name))
					this.setOption(cdata.id + param, v[cdata.name])
			}
							  	
			return this
		},
		//-------------------------------------------------------------------------------
		getOption: function(id) {
  		var data, oid, param, i, nv, ov, v
			//@d[
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")//@d]				
			
			i			= id.indexOf("#")
			oid 	= id.slice(0, i === -1 ? id.length : i)
			param	= id.slice(i === -1 ? id.length : i + 1)
			
			//@d[
			jsl.validate(oid, "qid", "id")//@d]

			data = this["__o_" + oid]//@d[
			if(!data)	throw jsl.ParamError("NotDefined:id")//@d]

			nv = this["__ov_"  + oid]
			ov = this["__oov_" + oid]
															
			try 			{ v = data.getter ? data.getter.call(this, nv, ov, oid, param) : nv 									}
			catch(e)	{ throw e.trace(this.getName() + ".get(v, oldv, id, param)", [ nv, ov, oid, param ])	}
      
      if(data.childs.length && !jsl.isObject(v)) throw jsl.MisuseError("BadReturnType:getter:object")
			
			param = param ? "#" + param : param
			for(var a = 0, len = data.childs.length; a < len; a++)
				v[data.childs[a].name] = this.getOption(data.childs[a].id + param)

			return v
	  },
		//-------------------------------------------------------------------------------
		getOldOption: function(id) {
  		var data, oid, param, i, ov, v
			//@d[
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")//@d]				
			
			i			= id.indexOf("#")
			oid 	= id.slice(0, i === -1 ? id.length : i)
			param	= id.slice(i === -1 ? id.length : i + 1)
			
			//@d[
			jsl.validate(oid, "qid", "id")//@d]

			data = this["__o_" + oid]//@d[
			if(!data)	throw jsl.ParamError("NotDefined:id")//@d]

			ov = this["__oov_" + oid]
																											
			try 			{ v = data.getter ? data.getter.call(this, ov, ov, oid, param) : ov 												}
			catch(e)	{ throw e.trace(this.getName() + ".getter(oldv, oldv, id, param)", [ ov, ov, oid, param ])	}
      
      if(data.childs.length && !jsl.isObject(v)) throw jsl.MisuseError("BadReturnType:getter:object")

			param = param ? "#" + param : param
			for(var a = 0, len = data.childs.length; a < len; a++)
				v[data.childs[a].name] = this.getOldOption(data.childs[a].id + param)

			return v
	  },			
		//-------------------------------------------------------------------------------
		isOption: function(id) {//@d[
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")//@d]
			
			return !!this["__o_" + id]
		},
		//-------------------------------------------------------------------------------
		getOptions: function(param) {
  		var os, v, map, p
			//@d[
			if(arguments.length > 1)	throw jsl.ParamError()//@d]
			
			os	= this.__options
			map	= this.__proto
			v		= {}
			
			while(map) {				
				
				//we can have some discontinuations (not jsl.OptionMap object) in the chain but we must go on 				
				if(map.__options)
					os = os.concat(map.__options)

				map	= map.__proto					
			}
			
			p	= arguments.length ? "#" + param : ""
							
			for(var a = 0, len = os.length; a < len; a++) 
        //the sub-options are automatically returned in parent options
				if(os[a].name === os[a].id)
					v[os[a].name] = this.getOption(os[a].id + p)                
			
      return v
	  },
		//-------------------------------------------------------------------------------
		setOptions: function(options, param) {
  		var os, map, p
			//@d[
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(options, "o", "options")//@d]
			
			os	= this.__options
			map	= this.__proto
			
			while(map) {				
				
				//we can have some discontinuations (not jsl.OptionMap object) in the chain but we must go on 				
				if(map.__options)
					os = os.concat(map.__options)

				map	= map.__proto					
			}

			p	= arguments.length ? "#" + param : ""
			
			for(var a = 0, len = os.length; a < len; a++) 
				//the sub-options are automatically filtred
				if(options.hasOwnProperty(os[a].id))
					this.setOption(os[a].id + p, options[os[a].id])
			
			return this
	  }
	})
